using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.CompilerServices;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Data
{
    public unsafe class BaseImageTexture<IColor> : ITexture, IDisposable
        where IColor : IColorType  
    {
        internal string filePath;
        private int width, height;
        private IColor[] internalData;

      
        public UV DuDv;

        public BaseImageTexture(string filePath, IColor[] data, int w, int h)
        {
            this.filePath = filePath;
            //this.internalData = new RgbSpectrum[w*h];
            //Array.Copy(data, this.internalData, w*h);
            this.internalData = data;
            //this.data = data;
            this.width = w;
            this.height = h;
            DuDv.U = 1f / Width;
            DuDv.V = 1f / Height;
        }
       

        #region ITexture Members

        public UV DUDV
        {
            get { return DuDv; }
        }
        public int Width
        {
            get { return width; }
        }

        public int Height
        {
            get { return height; }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public RgbSpectrumInfo Sample(float u, float v)
        {
            return Sample_Linear(u, v);

            float s = u * Width - 0.5f;
            float t = v * Height - 0.5f;

            int s0 = (int)Math.Floor(s);
            int t0 = (int)Math.Floor(t);

            float ds = s - s0;
            float dt = t - t0;

            float ids = 1f - ds;
            float idt = 1f - dt;
            unchecked
            {
                RgbSpectrum v00; 
                internalData[TexIndex(s0, t0)].TransformToRgb(out v00);
                RgbSpectrum v01; 
                internalData[TexIndex(s0, t0 + 1)].TransformToRgb(out v01);
                RgbSpectrum v02; 
                internalData[TexIndex(s0 + 1, t0)].TransformToRgb(out v02);
                RgbSpectrum v03;  
                internalData[TexIndex(s0 + 1, t0 + 1)].TransformToRgb(out v03);

                return
                    new RgbSpectrumInfo(
                        ids * idt * v00.c1 + ids * dt * v01.c1 + ds * idt * v02.c1 + ds * dt * v03.c1,
                        ids * idt * v00.c2 + ids * dt * v01.c2 + ds * idt * v02.c2 + ds * dt * v03.c2,
                        ids * idt * v00.c3 + ids * dt * v01.c3 + ds * idt * v02.c3 + ds * dt * v03.c3);
            }
        }

        public float SampleFloat(float u, float v)
        {
            return this.Sample(u, v).y();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public RgbSpectrumInfo Sample_Bilinear(float u, float v)
        {
            var s = u * Width - 0.5f;
            var t = v * Height - 0.5f;

            int s0 = (int)Math.Floor(s);
            int t0 = (int)Math.Floor(t);

            float ds = s - s0;
            float dt = t - t0;

            float ids = 1f - ds;
            float idt = 1f - dt;

            RgbSpectrum v00;
            internalData[TexIndex(s0, t0)].TransformToRgb(out v00);
            RgbSpectrum v01;
            internalData[TexIndex(s0, t0 + 1)].TransformToRgb(out v01);
            RgbSpectrum v02;
            internalData[TexIndex(s0 + 1, t0)].TransformToRgb(out v02);
            RgbSpectrum v03;
            internalData[TexIndex(s0 + 1, t0 + 1)].TransformToRgb(out v03);
            return
                new RgbSpectrumInfo(
                    ids * idt * v00.c1 + ids * dt * v01.c1 + ds * idt * v02.c1 + ds * dt * v03.c1,
                    ids * idt * v00.c2 + ids * dt * v01.c2 + ds * idt * v02.c2 + ds * dt * v03.c2,
                    ids * idt * v00.c3 + ids * dt * v01.c3 + ds * idt * v02.c3 + ds * dt * v03.c3);
        }


        public RgbSpectrumInfo Sample_Linear(float u, float v)
        {
            var s = u * Width - 0.5f;
            var t = v * Height - 0.5f;

            int s0 = (int)MathLab.Floor2UInt(s);
            int t0 = (int)MathLab.Floor2UInt(t);

            return (RgbSpectrumInfo)internalData[TexIndex(s0, t0)].ToXyz();
        }

        public void Sample_Linear(float u, float v, out RgbSpectrumInfo c)
        {
            var s = u * Width - 0.5f;
            var t = v * Height - 0.5f;

            int s0 = (int)MathLab.Floor2UInt(s);
            int t0 = (int)MathLab.Floor2UInt(t);

            c = (RgbSpectrumInfo)internalData[TexIndex(s0, t0)].ToXyz();
        }

        #endregion

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Mod(int a, int b)
        {
            if (b == 0)
                b = 1;
            a %= b;
            if (a < 0)
                a += b;
            return a;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int TexIndex(int s, int t)
        {
            int u = Mod(s, Width);
            int v = Mod(t, Height);
            int index = v * Width + u;
            return index;
        }

        private RgbSpectrumInfo GetTexel(int s, int t)
        {

            int u = Mod(s, Width);
            int v = Mod(t, Height);
            int index = v * Width + u;
            return (RgbSpectrumInfo)internalData[index].ToXyz();
        }


        public static RgbSpectrumTexture GdiLoadBitmap(Bitmap bmp)
        {
            var pdata = new RgbSpectrum[bmp.Width * bmp.Height];

            for (int i = 0; i < bmp.Height; i++)
            {
                for (int j = 0; j < bmp.Width; j++)
                {
                    pdata[j + i * bmp.Width] =
                        //RgbSpectrum.Create(bmp.GetPixel(j, i));
                        RgbSpectrum.Create(bmp.GetPixel(j, i).R, bmp.GetPixel(j, i).G, bmp.GetPixel(j, i).B, false);

                }
            }
            var tex = new RgbSpectrumTexture(string.Empty, pdata, bmp.Width, bmp.Height);
            return tex;
        }

        /*
                public static RgbSpectrumTexture FreeImageLoadExrBitmap( string fileName ) {
                    using( var bmp = new FreeImageBitmap(fileName) ) {
                        var pdata = new Spectrum[bmp.Width*bmp.Height];
                        for( int i = 0; i < bmp.Height; i++ ) {
                            Scanline<FIRGBF> s = bmp.GetScanline<FIRGBF>(i);
                            for( int j = 0; j < bmp.Width; j++ ) {
                                pdata[j + i*bmp.Width] = new Spectrum(s[j].red, s[j].green, s[j].blue);
                            }
                        }
                        var tex = new SpectrumTexture(pdata, bmp.Width, bmp.Height);
                        return tex;
                    }
                }

                public static SpectrumTexture FreeImageLoadBitmap( string fileName ) {
                    using( var bmp = new FreeImageBitmap(fileName) ) {
                        var pdata = new Spectrum[bmp.Width * bmp.Height];
                        for( int i = 0; i < bmp.Height; i++ ) {
                            Scanline<RGBTRIPLE> s = bmp.GetScanline<RGBTRIPLE>(i);
                            for( int j = 0; j < bmp.Width; j++ ) {
                                pdata[j + i * bmp.Width] = Spectrum.Create(s[j].rgbtRed, s[j].rgbtGreen, s[j].rgbtBlue);
                            }
                        }
                        var tex = new SpectrumTexture(pdata, bmp.Width, bmp.Height);
                        return tex;
                    }
                }
                              */

        public static RgbSpectrumTexture UnsafeLoadFromBitmap(Bitmap bmp)
        {
            var pdata = new RgbSpectrum[bmp.Width * bmp.Height];
            BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
            unsafe
            {
                byte* ptr = (byte*)data.Scan0.ToPointer();
                int offset = data.Stride - bmp.Width * 4;

                for (int i = 0; i < bmp.Height; i++)
                {
                    for (int j = 0; j < bmp.Width; j++, ptr += 4)
                    {


                        var r = ptr[2];
                        var g = ptr[1];
                        var b = ptr[0];

                        pdata[j + i * bmp.Width] = RgbSpectrum.Create(r, g, b, true);

                    }
                    ptr += offset;
                }
            }
            bmp.UnlockBits(data);

            var tex = new RgbSpectrumTexture(string.Empty, pdata, bmp.Width, bmp.Height);
            return tex;
        }

        #region ITexture Members


        public void Dispose()
        {
            //this.data = null;
            width = 0;
            height = 0;
        }

        #endregion

        public override string ToString()
        {
            return string.Format("{0}", filePath ?? this.GetType().Name);
        }
    }
}